-- Astra.nvim 二进制文件管理模块
-- 负责二进制文件的验证、构建、下载和安装

local M = {}

-- 安全的 Vim API 访问（用于测试模式）
local safe_vim = {}
if vim and vim.fn then
  safe_vim.fn = vim.fn
else
  safe_vim.fn = {
    expand = function(expr)
      if expr == "%:p" then
        return ""
      elseif expr == "%:t" then
        return ""
      elseif expr == "%:p:h" then
        return ""
      elseif expr:match("^~") then
        return os.getenv("HOME") .. expr:sub(2)
      end
      return expr
    end,
    executable = function(path)
      return os.execute("test -x " .. path .. " 2>/dev/null") == 0 and 1 or 0
    end,
    getcwd = function()
      return "."
    end,
    system = function(cmd)
      local handle = io.popen(cmd)
      local result = handle:read("*all")
      handle:close()
      return result
    end,
    filereadable = function(path)
      local file = io.open(path, "r")
      if file then
        io.close(file)
        return 1
      end
      return 0
    end,
    writefile = function(lines, path)
      local file = io.open(path, "w")
      if file then
        for _, line in ipairs(lines) do
          file:write(line .. "\n")
        end
        io.close(file)
        return true
      end
      return false
    end
  }
end

-- 二进制文件路径配置
M.paths = {
  plugin = safe_vim.fn.expand("~/.local/share/nvim/lazy/astra.nvim"),
  release = "target/release/astra-core",
  debug = "target/debug/astra-core",
  static = "target/x86_64-unknown-linux-musl/release/astra-core"
}

-- 获取二进制文件路径
function M.get_binary_path()
  -- 1. 优先检查本地项目构建
  local local_debug = safe_vim.fn.getcwd() .. "/astra-core/" .. M.paths.debug
  if safe_vim.fn.executable(local_debug) == 1 then
    return local_debug
  end

  -- 2. 检查插件目录中的各种构建
  local plugin_dir = M.paths.plugin

  local plugin_paths = {
    plugin_dir .. "/astra-core/" .. M.paths.debug,
    plugin_dir .. "/astra-core/" .. M.paths.release,
    plugin_dir .. "/astra-core/" .. M.paths.static
  }

  for _, path in ipairs(plugin_paths) do
    if safe_vim.fn.executable(path) == 1 then
      return path
    end
  end

  return nil
end

-- 验证二进制文件
function M.validate()
  local binary_path = M.get_binary_path()

  if not binary_path then
    return {
      available = false,
      path = nil,
      reason = "No binary file found",
      suggestion = "Run :AstraBuild or :AstraInstall"
    }
  end

  -- 测试二进制文件是否可执行
  local test_result = safe_vim.fn.system(binary_path .. " --help", "")
  if os.execute(binary_path .. " --help >/dev/null 2>&1") ~= 0 then
    return {
      available = false,
      path = binary_path,
      reason = "Binary file is not executable",
      suggestion = "Rebuild with :AstraBuild"
    }
  end

  return {
    available = true,
    path = binary_path,
    version = M._get_version(binary_path),
    type = M._get_build_type(binary_path)
  }
end

-- 获取二进制版本
function M._get_version(binary_path)
  local output = vim.fn.system(binary_path .. " --version", "")
  if vim.v.shell_error == 0 then
    return output:gsub("\n", ""):match("version (%S+)") or "unknown"
  end
  return "unknown"
end

-- 获取构建类型
function M._get_build_type(binary_path)
  if binary_path:match("debug") then
    return "debug"
  elseif binary_path:match("static") then
    return "static"
  else
    return "release"
  end
end

-- 从构建输出中提取有用的错误信息
function M._extract_useful_errors(build_output)
  local useful_errors = {}
  local error_patterns = {
    "error:",
    "error%[",
    "cannot find",
    "not found",
    "permission denied",
    "no such file",
    "failed",
    "warning:",
    "note:",
    "help:",
  }

  for _, line in ipairs(build_output) do
    if line and line ~= "" then
      local line_lower = line:lower()

      -- 检查是否包含错误关键词
      for _, pattern in ipairs(error_patterns) do
        if line_lower:match(pattern) then
          table.insert(useful_errors, line)
          break -- 避免重复添加同一行
        end
      end
    end
  end

  -- 如果没有找到有用错误，返回最后几行输出
  if #useful_errors == 0 and #build_output > 0 then
    local start_idx = math.max(1, #build_output - 5)
    for i = start_idx, #build_output do
      if build_output[i] and build_output[i] ~= "" then
        table.insert(useful_errors, build_output[i])
      end
    end
  end

  return useful_errors
end

-- 构建二进制文件
function M.build()
  local plugin_dir = M.paths.plugin
  local build_cmd = string.format("cd %s && make build", plugin_dir)

  -- 使用 Snacks.nvim 的通知系统，通过 id 来更新同一个通知
  local build_id = "astra_build"
  local spinner = { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }
  local spinner_idx = 1

  -- 显示初始构建通知
  vim.notify("🔨 开始构建 Astra 核心二进制文件...", vim.log.levels.INFO, {
    id = build_id,
    title = "Astra 构建",
    timeout = false, -- 保持显示直到手动关闭或替换
  })

  -- 启动 spinner 更新
  local spinner_timer = vim.uv.new_timer()
  spinner_timer:start(100, 100, function()
    vim.notify("🔨 正在构建 Astra 核心二进制文件... " .. spinner[spinner_idx], vim.log.levels.INFO, {
      id = build_id,
      title = "Astra 构建",
      timeout = false,
    })
    spinner_idx = (spinner_idx % #spinner) + 1
  end)

  local build_output = {} -- 收集构建输出
  local has_errors = false

  local job = vim.fn.jobstart(build_cmd, {
    on_stdout = function(_, data)
      if data and #data > 0 then
        for _, line in ipairs(data) do
          if line and line ~= "" then
            table.insert(build_output, line)
            -- 只在调试模式下显示详细输出
            if vim.env.ASTRA_DEBUG then
              vim.notify("Build: " .. line, vim.log.levels.DEBUG)
            end
          end
        end
      end
    end,
    on_stderr = function(_, data)
      if data and #data > 0 then
        for _, line in ipairs(data) do
          if line and line ~= "" then
            table.insert(build_output, line)
            has_errors = true
            -- 收集错误信息，但不立即显示
          end
        end
      end
    end,
    on_exit = function(_, exit_code)
      -- 停止 spinner
      if spinner_timer then
        spinner_timer:close()
      end

      if exit_code == 0 then
        -- 构建成功
        local status = M.validate()
        local success_msg = string.format("✅ Astra 核心二进制文件构建成功！\n🚀 二进制文件: %s (%s)", status.path, status.type)

        vim.notify(success_msg, vim.log.levels.INFO, {
          id = build_id,
          title = "Astra 构建",
          icon = "✅",
          timeout = 5000,
        })

        -- 重新初始化核心模块
        local Core = require("astra.core")
        Core.reinitialize()
      else
        -- 构建失败，显示有用的错误信息
        local error_lines = {}
        table.insert(error_lines, "❌ Astra 核心二进制文件构建失败")
        table.insert(error_lines, "退出码: " .. exit_code)
        table.insert(error_lines, "")

        -- 从构建输出中提取有用的错误信息
        local useful_errors = M._extract_useful_errors(build_output)
        for _, error_line in ipairs(useful_errors) do
          table.insert(error_lines, error_line)
        end

        if #useful_errors == 0 then
          table.insert(error_lines, "💡 请检查 Rust 工具链是否已正确安装")
        end

        vim.notify(table.concat(error_lines, "\n"), vim.log.levels.ERROR, {
          id = build_id,
          title = "Astra 构建失败",
          icon = "❌",
          timeout = 8000, -- 错误信息显示更长时间
        })
      end
    end
  })

  if job <= 0 then
    -- 停止 spinner
    if spinner_timer then
      spinner_timer:close()
    end
    vim.notify("❌ 无法启动构建进程", vim.log.levels.ERROR, {
      id = build_id,
      title = "Astra 构建错误",
      icon = "❌",
      timeout = 5000,
    })
  end

  return job > 0
end

-- 安装预编译二进制文件
function M.install()
  vim.notify("📦 Installing precompiled Astra core binary...", vim.log.levels.INFO)

  -- 这里可以实现从 GitHub Releases 下载预编译版本
  -- 暂时提示用户手动构建
  vim.notify("🔧 Precompiled binaries not yet available", vim.log.levels.WARN)
  vim.notify("💡 Please run :AstraBuild to compile from source", vim.log.levels.INFO)

  -- 检查系统信息
  local system_info = {
    os = vim.loop.os_uname().sysname,
    arch = vim.loop.os_uname().machine
  }

  vim.notify(string.format("📊 System: %s %s", system_info.os, system_info.arch), vim.log.levels.INFO)
end

-- 显示二进制信息
function M.info()
  local status = M.validate()

  -- 构建信息消息
  local info_lines = {}
  table.insert(info_lines, "🔍 Astra 二进制文件信息:")
  table.insert(info_lines, "  状态: " .. (status.available and "✅ 可用" or "❌ 不可用"))

  if status.available then
    table.insert(info_lines, "  路径: " .. status.path)
    table.insert(info_lines, "  版本: " .. status.version)
    table.insert(info_lines, "  类型: " .. status.type)
  else
    table.insert(info_lines, "  原因: " .. status.reason)
    table.insert(info_lines, "  建议: " .. status.suggestion)
  end

  -- 构建搜索路径信息（仅在调试模式下显示）
  if vim.env.ASTRA_DEBUG then
    table.insert(info_lines, "")
    table.insert(info_lines, "📁 搜索路径:")
    local plugin_dir = M.paths.plugin
    local paths_to_check = {
      "Local debug: " .. vim.fn.getcwd() .. "/astra-core/target/debug/astra-core",
      "Plugin debug: " .. plugin_dir .. "/astra-core/target/debug/astra-core",
      "Plugin release: " .. plugin_dir .. "/astra-core/target/release/astra-core",
      "Plugin static: " .. plugin_dir .. "/astra-core/target/x86_64-unknown-linux-musl/release/astra-core"
    }

    for _, path in ipairs(paths_to_check) do
      local exists = vim.fn.executable(path:match(": (.+)")) == 1
      table.insert(info_lines, "  " .. (exists and "✅" or "❌") .. " " .. path)
    end
  end

  -- 一次性显示所有信息
  vim.notify(table.concat(info_lines, "\n"), vim.log.levels.INFO, {
    title = "Astra 二进制信息",
    timeout = 6000,
  })
end

-- 清理构建文件
function M.clean()
  local plugin_dir = M.paths.plugin
  local clean_cmd = string.format("cd %s && make clean", plugin_dir)

  -- 使用相同的 id 来更新清理通知
  local clean_id = "astra_clean"

  vim.notify("🧹 正在清理构建文件...", vim.log.levels.INFO, {
    id = clean_id,
    title = "Astra 清理",
    timeout = false,
  })

  local job = vim.fn.jobstart(clean_cmd, {
    on_exit = function(_, exit_code)
      if exit_code == 0 then
        vim.notify("✅ 构建文件清理成功！", vim.log.levels.INFO, {
          id = clean_id,
          title = "Astra 清理",
          icon = "✅",
          timeout = 3000,
        })
      else
        vim.notify("❌ 清理失败，退出码: " .. exit_code, vim.log.levels.ERROR, {
          id = clean_id,
          title = "Astra 清理失败",
          icon = "❌",
          timeout = 5000,
        })
      end
    end
  })
end

return M